/*
 * This code is a minimal hardware described in Chisel.
 * 
 * Blinking LED: the FPGA version of Hello World
 */

import chisel3._
import chisel3.util._
//import _root_.circt.stage.ChiselStage

class DecoupledIO[T <: Data](gen: T) extends Bundle {
    val ready = Input(Bool())
    val valid = Output(Bool())
    val bits = Output(gen)
}
class FifoIO[T <: Data](private val gen: T) extends Bundle {
    val enq = Flipped(new DecoupledIO(gen))
    val deq = new DecoupledIO(gen)
}

abstract class Fifo[T <: Data](gen: T, val depth: Int) extends Module {
    val io = IO(new FifoIO(gen))
    assert(depth > 0, "Number of buffer elements needs to be larger than 0.")
}

class RegFifo[T <: Data](gen: T, depth: Int) extends Fifo(gen: T, depth: Int) {
    def counter(depth: Int, incr: Bool): (UInt, UInt) = {
        val cntReg = RegInit(0.U(log2Ceil(depth).W))
        val nextVal = Mux(cntReg === (depth-1).U, 0.U, cntReg + 1.U)
        when (incr) {
            cntReg := nextVal
        }
        (cntReg, nextVal)
    }

    // 基于寄存器的内存
    val memReg = Reg(Vec(depth, gen))

    val incrRead = WireDefault(false.B)
    val incrWrite = WireDefault(false.B)
    val (readPtr, nextRead) = counter(depth, incrRead)
    val (writePtr, nextWrite) = counter(depth, incrWrite)

    val emptyReg = RegInit(true.B)
    val fullReg = RegInit(false.B)

    when (io.enq.valid && !fullReg) {
        memReg(writePtr) := io.enq.bits
        emptyReg := false.B
        fullReg := nextWrite === readPtr
        incrWrite := true.B
    }

    when (io.deq.ready && !emptyReg) {
        fullReg := false.B
        emptyReg := nextRead === writePtr
        incrRead := true.B
    }

    io.deq.bits := memReg(readPtr)
    io.enq.ready := !fullReg
    io.deq.valid := !emptyReg
}

class MemFifo[T <: Data](gen: T, depth: Int) extends Fifo(gen: T, depth: Int) {
    def counter(depth: Int, incr: Bool): (UInt, UInt) = {
        val cntReg = RegInit(0.U(log2Ceil(depth).W))
        val nextVal = Mux(cntReg === (depth-1).U, 0.U, cntReg + 1.U)
        when (incr) {
            cntReg := nextVal
        }
        (cntReg, nextVal)
    }

    val mem = SyncReadMem(depth, gen)

    val incrRead = WireDefault(false.B)
    val incrWrite = WireDefault(false.B)
    val (readPtr, nextRead) = counter(depth, incrRead)
    val (writePtr, nextWrite) = counter(depth, incrWrite)

    val emptyReg = RegInit(true.B)
    val fullReg = RegInit(false.B)

    val idle :: valid :: full :: Nil = Enum(3)
    val stateReg = RegInit(idle)
    val shadowReg = Reg(gen)

    // 写FIFO的处理是一样的
    when (io.enq.valid && !fullReg) {
        mem.write(writePtr, io.enq.bits)
        emptyReg := false.B
        fullReg := nextWrite === readPtr
        incrWrite := true.B
    }

    // 读基于内存的FIFO时要处理一个时钟周期的延迟
    val data = mem.read(readPtr)

    switch(stateReg) {
        is(idle) {
            when(!emptyReg) {
                stateReg := valid
                fullReg := false.B
                emptyReg := nextRead === writePtr
                incrRead := true.B
            }
        }
        is(valid) {
            when(io.deq.ready) {
                when(!emptyReg) {
                    stateReg := valid
                    fullReg := false.B
                    emptyReg := nextRead === writePtr
                    incrRead := true.B
                }.otherwise {
                    stateReg := idle
                }
            }.otherwise {
                shadowReg := data
                stateReg := full
            }
        }
        is(full) {
            when(io.deq.ready) {
                when(!emptyReg) {
                    stateReg := valid
                    fullReg := false.B
                    emptyReg := nextRead === writePtr
                    incrRead := true.B
                }.otherwise {
                    stateReg := idle
                }
            }
        }
    }

    io.deq.bits := Mux(stateReg === valid, data, shadowReg)
    io.enq.ready := !fullReg
    io.deq.valid := stateReg === valid || stateReg === full
}

object myfifo extends App {
  (new chisel3.stage.ChiselStage).emitVerilog(new MemFifo(UInt(8.W),8))
  (new chisel3.stage.ChiselStage).emitVerilog(new RegFifo(UInt(8.W),8))
}

//object myfifo extends App {
//  ChiselStage.emitSystemVerilogFile(new MemFifo(8.U,8),firtoolOpts = Array("-disable-all-randomization", "-strip-debug-info"))
//  ChiselStage.emitSystemVerilogFile(new RegFifo(8.U,8),firtoolOpts = Array("-disable-all-randomization", "-strip-debug-info"))
//}


